/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.common.misc.util;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;
import org.vacoor.nothing.common.util.IOUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Enumeration;
/*
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
*/

/**
 * <p>
 * 简单的zip压缩解压工具类
 * 代码适用于java.util.zip 和 org.apache.tools.zip(ant.jar)
 * 两者API基本相同, 效率差别不大, 只需导入相应类即可,
 * JDK 存在中文文件名乱码和无法解压 (1.7 开始支持指定编码)
 * apache zip可以通过ZipOutputStream.setEncoding / new ZipFile(file, encoding)
 * 来设置编码,解决压缩中文乱码问题
 * 如果使用 ant.jar
 * 可以使用Expand类来简化操作:
 * </p>
 * <p/>
 * <pre>
 * Expand e = new Expand();
 * e.setProject(new Project());
 * e.setSrc(new File("test2.zip"));
 * e.setOverwrite(true);
 * e.setDest(new File("test"));
 * e.setEncoding(System.getProperty("file.encoding"));
 * e.execute();
 * </pre>
 *
 * @author Vacoor
 */
@SuppressWarnings({"unused"})
public abstract class ZipUtils {
    private static final int DEFAULT_BUFF_SIZE = 4096;

    /**
     * 将给定文件压缩为给定文件
     *
     * @param files       要压缩的文件/目录
     * @param archivePath 压缩文件路径eg: /home/test.org.vacoor/test.zip, test.zip
     * @param charset     压缩包使用的字符编码
     * @throws IOException
     */
    public static void compressTo(File[] files, File archivePath, Charset charset) throws IOException {
        File dir = archivePath.getParentFile();
        if (!dir.exists()) {
            dir.mkdirs();
        }
        compressTo(files, new FileOutputStream(archivePath), charset);
    }

    /**
     * 将给定文件压缩到给定流
     *
     * @param files   要压缩的文件
     * @param out     目标流
     * @param charset 压缩包使用的字符编码
     * @throws IOException
     */
    public static void compressTo(File[] files, OutputStream out, Charset charset) throws IOException {
        BufferedOutputStream bos = out instanceof BufferedOutputStream ? (BufferedOutputStream) out : new BufferedOutputStream(out, DEFAULT_BUFF_SIZE);
        // for jdk 1.7
        // ZipOutputStream zipOut = new ZipOutputStream(bos, charset);
        // for apache zip
        ZipOutputStream zipOut = new ZipOutputStream(bos);
        zipOut.setEncoding(charset.name());
        zipOut.setFallbackToUTF8(true);

        // 遍历所有文件进行压缩
        for (File f : files) {
            compressEntry(f, f.getCanonicalFile().getParentFile().getPath(), zipOut);
        }
        zipOut.flush();
        zipOut.close();
    }

    /**
     * 递归压缩文件/目录到zip流
     *
     * @param file
     * @param baseDir 基准目录,所有entry将以该目录为基准进行创建
     * @param zipOut
     * @throws IOException
     */
    private static void compressEntry(File file, String baseDir, ZipOutputStream zipOut) throws IOException {
        String relativePath = file.getCanonicalPath().substring(baseDir.length());
        // 如果要添加一个目录,注意使用符号"/"作为添加项名字结尾符
        relativePath = file.isDirectory() && !relativePath.endsWith("/") ? relativePath + "/" : relativePath;

        // 添加一个 entry
        zipOut.putNextEntry(new ZipEntry(relativePath));
        if (file.isFile()) { // 如果是文件, 写入文件内容
            IOUtils.flow(new FileInputStream(file), zipOut, true, false);
        }
        zipOut.closeEntry();

        // 如果是目录遍历其下所有文件
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                compressEntry(f, baseDir, zipOut);
            }
        }
    }


    /*   extract   */

    /**
     * 解压文件到指定目录
     * 该方法基本也与Aapache Zip 使用相同
     *
     * @param archiveFile zip归档文件
     * @param outputDir   输出目录
     * @throws java.io.IOException
     */
    public static void extract(File archiveFile, String outputDir, Charset charset) throws IOException {
        File outDir = new File(outputDir);
        if (!outDir.exists()) {
            outDir.mkdirs();
        }

        // jdk 1.7
        // ZipFile zipFile = new ZipFile(archiveFile, charset);
        // Enumeration<? extends ZipEntry> entries = zipFile.entries();
        // apache zip
        ZipFile zipFile = new ZipFile(archiveFile, charset.name());
        Enumeration<? extends ZipEntry> entries = zipFile.getEntries();

        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            File entryPath = new File(outDir.getPath() + "/" + entry.getName());
            if (entry.isDirectory()) {
                entryPath.mkdirs();
                continue;
            }

            if (!entryPath.getParentFile().exists()) {
                entryPath.getParentFile().mkdirs();
            }

            InputStream is = zipFile.getInputStream(entry);
            FileOutputStream fos = new FileOutputStream(entryPath);
            IOUtils.flow(is, fos, new byte[DEFAULT_BUFF_SIZE], true, true);
        }
    }

    private ZipUtils() {
    }
}
